Exceptions¶
In an ideal situation, our program runs smoothly without any errors. However it is not always the case. Errors may be due to developer’s fault or programmer’s mistake or of computer. Source of some errors might be hard to undertsand. However it is the task of Good Programmer to handle all kinds of errors that might occur in his program. If some error condition escapes from the developer and user catches it, It is a bug in the program. Developers must update the programs periodically to fix the bugs in the software. You may remember that recent ransomware attack which caused the loss of enormous amount of data, was due to a bug in Microsoft Windows.
Facing a first exception¶
Let’s write a lambda to divide 2 numbers
In [1]:
div = lambda x,y : x/y
In [2]:
div(8,2)
Out[2]:
4.0
In [3]:
div(0/0)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-3-d742f81f0a4a> in <module>()
----> 1 div(0/0)
ZeroDivisionError: division by zero
Oh No!… It was a error. Let’s handle it.
try
-except
-finally
¶
try-except-finally
provides an easy way to handle errors that can
arise during program execution. It works similar to
try-catch-finally
blocks in Java
and C#
Syntax:
try:
<statement 1>
<statement 2>
...
<statement n>
except (Exception List): # Refer note
<statement 1>
<statement 2>
...
<statement n>
finally:
<cleanup 1>
<cleanup 2>
...
<cleanup n>
Note:
finally
block is optional- If Exception List is empty all exceptions are handled by
except
block - If catching a single exception, it can be referred with its name.
except RangeError as e:
<do-something-with-e>
- Base Exception classes must be captured at last, if catching exceptions in hierarchy
div
with exception handling¶
In [5]:
def div_good(x,y):
try:
return x/y
except ZeroDivisionError:
print("Division by zero")
In [6]:
div_good(8,2)
Out[6]:
4.0
In [7]:
div_good(0,0)
Division by zero
Note how the exception was handled
Cleaning the things up¶
In this version of div
, we will return a NaN
if a
ZeroDivisionError
occures. 'NaN'
is Not a Number. 'Inf'
refers infinity
In [4]:
def div_clean(x,y):
try:
value = x/y
except ZeroDivisionError:
value = float('NaN')
return value
In [2]:
div_clean(4,3)
Out[2]:
1.3333333333333333
In [3]:
div_clean(8,0)
Out[3]:
nan
Raising Exceptions¶
The raise
statement allows the programmer to force a specified
exception to occur. For example:
In [5]:
raise NameError('HiThere')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-5-93385ba972b1> in <module>()
----> 1 raise NameError('HiThere')
NameError: HiThere
The sole argument to raise
indicates the exception to be raised.
This must be either an exception instance or an exception class (a class
that derives from Exception). If an exception class is passed, it will
be implicitly instantiated by calling its constructor with no arguments:
In [6]:
raise ValueError # shorthand for 'raise ValueError()'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-6-f4e87a14b34e> in <module>()
----> 1 raise ValueError # shorthand for 'raise ValueError()'
ValueError:
If you need to determine whether an exception was raised but don’t
intend to handle it, a simpler form of the raise
statement allows
you to re-raise the exception:
In [7]:
try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
raise
An exception flew by!
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-7-3f47609917d7> in <module>()
1 try:
----> 2 raise NameError('HiThere')
3 except NameError:
4 print('An exception flew by!')
5 raise
NameError: HiThere
User-defined Exceptions¶
Programs may name their own exceptions by creating a new exception
class. Exceptions should typically be derived from the Exception
class, either directly or indirectly.
Exception classes can be defined which do anything any other class can do, but are usually kept simple, often only offering a number of attributes that allow information about the error to be extracted by handlers for the exception. When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions:
In [1]:
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not
allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
Most exceptions are defined with names that end in Error
, similar to
the naming of the standard exceptions.